﻿using Furion.Authorization;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Furion.CMS.Core;
using Furion.CMS.Core.Permission;
using System.ComponentModel;
using Furion.CMS.Application;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;

namespace Furion.CMS.Web.Core
{
    /// <summary>
    /// JWT 授权自定义处理程序
    /// </summary>
    public class JwtHandler : AppAuthorizeHandler
    {
        private readonly IDistributedCache<IEnumerable<string>> PermissionCache;
        private readonly IDistributedCache<long> SessionCaches;

        public JwtHandler(IDistributedCache<IEnumerable<string>> permissionCache,
            IDistributedCache<long> sessionCaches)
        {
            PermissionCache = permissionCache;
            SessionCaches = sessionCaches;
        }

        /// <summary>
        /// 重写 Handler 添加自动刷新收取逻辑
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task HandleAsync(AuthorizationHandlerContext context)
        {
            // 自动刷新 token
            if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext()))
            {
                await AuthorizeHandleAsync(context);
            }
            else context.Fail();    // 授权失败
        }

        /// <summary>
        /// 验证管道，也就是验证核心代码
        /// </summary>
        /// <param name="context"></param>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public override async Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
        {
            // 检查会话安全
            var uid = context.User.FindFirstValue(AppClaimConsts.UserId);
            var sid = context.User.FindFirstValue(AppClaimConsts.SessionId);
            var key = string.Format(CacheKeyConsts.SessionSafetyIdentify, uid);
            var sessionId = await SessionCaches.GetAsync(key);
            var isSuccess = long.TryParse(sid, out var tokenSid) && tokenSid == sessionId;
            if (isSuccess)
            {
                // 权鉴验证通过则刷新缓存
                await SessionCaches.RefreshAsync(key);
                // 检查权限，如果方法时异步的就不用 Task.FromResult 包裹，直接使用 async/await 即可
                isSuccess = await CheckAuthorzieAsync(httpContext);
            }
            else
            {
                httpContext.Response.Headers.Add("X-SessionSafetyIdentify", "0");
            }

            return isSuccess;
        }

        /// <summary>
        /// 根据默认命名规则获取资源id
        /// </summary>
        /// <param name="descriptor"></param>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        private string GetDefaultResourceId(Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor descriptor, DefaultHttpContext httpContext)
        {
            var groupName = PermissionGroupDefinition.PermissionGroupNamed(descriptor.ControllerTypeInfo.Name);
            var displayNameAttr = httpContext.GetMetadata<DisplayNameAttribute>();
            if (displayNameAttr != null && !string.IsNullOrWhiteSpace(displayNameAttr.DisplayName))
            {// 资源组名称替换为指定的名称
                groupName = displayNameAttr.DisplayName;
            }
            // 从controller的自定义标签中获取安全定义
            var securityDefineAttribute = descriptor.ControllerTypeInfo.GetCustomAttributes(typeof(SecurityDefineAttribute), true)
                 .Cast<SecurityDefineAttribute>().FirstOrDefault();
            if (securityDefineAttribute != null) groupName = securityDefineAttribute.ResourceId;

            var resourceName = PermissionGroupDefinition.PermissionNamed(descriptor.MethodInfo.Name);

            return $"{groupName}_{resourceName}";
        }

        /// <summary>
        /// 检查权限
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        private async Task<bool> CheckAuthorzieAsync(DefaultHttpContext httpContext)
        {
            /*
             * 获取安全定义特性
             * 若不存在则认为当前资源不进行访问限制
             */
            var securityDefineAttribute = httpContext.GetMetadata<SecurityDefineAttribute>();
            if (securityDefineAttribute == null) return true;

            /*
             * 获取actionDescriptor
             * 若安全定义未设置资源id时,则采用 Controller_Action 来作为默认资源id
             */
            var actionDescriptor = httpContext.GetControllerActionDescriptor();

            var currentResourceId = GetDefaultResourceId(actionDescriptor, httpContext); //获取默认资源Id
            if (!string.IsNullOrWhiteSpace(securityDefineAttribute.ResourceId))
            {// 从安全定义中更新资源Id
                currentResourceId = securityDefineAttribute.ResourceId;
            }

            // 获取当前身份凭据中的信息
            var userId = httpContext.User.FindFirstValue(AppClaimConsts.UserId);
            var tenantId = httpContext.User.FindFirstValue(AppClaimConsts.TenantId);
            var roleList = httpContext.User.FindFirstValue(AppClaimConsts.RoleIdList)?
                .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
            var orgList = httpContext.User.FindFirstValue(AppClaimConsts.OrgIdList)?
                .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];

            // 生成用于判断资源是否可用的集合
            var currUserResourceValues = roleList.Select(s => string.Format(CacheKeyConsts.PermissionResourceValue, s, userId));
            // 添加从机构继承的角色
            var orgRoleResourceValues = orgList.Select(s => string.Format(CacheKeyConsts.PermissionResourceValue, s, userId));
            currUserResourceValues = currUserResourceValues.Union(orgRoleResourceValues);
            // 添加独立模块授权值标识
            currUserResourceValues = currUserResourceValues.Append(string.Format(CacheKeyConsts.PermissionResourceValue, Guid.Empty, userId));

            // 从缓存中获取权限数据并校验是否有权限使用当前资源
            var permissionKey = string.Format(CacheKeyConsts.PermissionResource, currentResourceId, tenantId);
            var permissionList = await PermissionCache.GetOrAddAsync(permissionKey, async () =>
            {
                var PermissionManager = App.GetService<PermissionAppService>();
                // 获取人员独立拥有的资源权限
                var independentUsers = await PermissionManager.GetAssignUserListAsync(currentResourceId);
                var result = new List<string>(independentUsers.Select(s => string.Format(CacheKeyConsts.PermissionResourceValue, Guid.Empty, s.Id)));
                // 获取角色所拥有的资源权限
                var items = await PermissionManager.GetAssignRoleListAsync(currentResourceId);
                foreach (var item in items)
                {
                    var roleUsers = item.Users.Select(s => string.Format(CacheKeyConsts.PermissionResourceValue, item.Code, s.Id));
                    result.AddRange(roleUsers);
                }

                return result;
            });

            // 先检查是否是 "SuperAdmin" 策略
            var roleModuleKeys = roleList.Select(s => string.Format(CacheKeyConsts.PermissionModule, s));
            var moduleList = await PermissionCache.GetManyAsync(roleModuleKeys);
            if (moduleList.Where(t => t.Value != null).SelectMany(s => s.Value).Contains(LocalPolicyConsts.SuperAdmin))
            { // 如果用户具备 "SuperAdmin" 策略则拥有所有的资源访问权限
                return true;
            }

            // 从权限列表中进行判断
            return permissionList?.Any(a => currUserResourceValues.Contains(a)) ?? false;
        }
    }
}
